/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
/* 
This File was written as part of HW assignment 3 in the course "Introduction
To Systems Programming" at Tel-Aviv University's School of Electrical 
Engineering, Winter 2012, by Amnon Drory. 
*/ 
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

#define  _CRT_SECURE_NO_WARNINGS

#include "Bmp24LineReader.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

/*======================*/
/* BMP File Format Data */
/*======================*/

#define BMP24_BYTES_PER_PIXEL				3
#define BITS_PER_BYTE						8
#define BMP24_BITS_PER_PIXEL				( BMP24_BYTES_PER_PIXEL * BITS_PER_BYTE )

#define BI_RGB								0 // code for compression method "no compression"
#define BMP24_COMPRESSION_METHOD			BI_RGB

#define POSITION_OF_BITS_PER_PIXEL			0x1C
#define SIZEOF_BITS_PER_PIXEL				2
#define POSITION_OF_COMPRESSION_METHOD		0x1E
#define SIZEOF_COMPRESSION_METHOD			4
#define POSITION_OF_WIDTH					18
#define SIZEOF_WIDTH						4
#define POSITION_OF_HEIGHT					22
#define SIZEOF_HEIGHT						4
#define POSITION_OF_LOCATION_OF_IMAGE_DATA	10
#define SIZEOF_LOCATION_OF_IMAGE_DATA		4

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

/*=======*/
/* Types */
/*=======*/

typedef struct 
{
	FILE* File;
	int Width;
	int Height;
	int Pitch;
	int CurLineIndex;
}
Bmp24ReaderData;

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

/*=================*/
/* Macro Functions */
/*=================*/
/**
 * READ_FIELD_FROM_FILE() reads one data field from the Bitmap file.
 *
 * Arguments
 * ---------
 * FieldName - the name of the field. Must be one of the fields for which 
 *			   the macro constants POSITION_OF_* and SIZEOF_* are defined.
 * Destination - address of the variable into which the field will be written.
 * RawReader - pointer to a Bmp24ReaderData struct representing a Line Reader object.
 * Success - output parameter. Will contain TRUE if the field was read successfully,
 *           FALSE otherwise.
 */ 
#define READ_FIELD_FROM_FILE( FieldName, Destination, RawReader, Success )  \
	do																		\
	{																		\
		int Res;															\
		if ( sizeof(*Destination) != SIZEOF_ ## FieldName ## )				\
		{																	\
			printf("Trying to read data from file into variable of " 		\
				   "incompatible size. ending program\n");					\
			exit(1);														\
		}																	\
																			\
		Res = fseek( RawReader->File,										\
			POSITION_OF_ ## FieldName ##, 									\
			SEEK_SET ); /* position relative to start */					\
																			\
		Success = ( Res == 0 );												\
																			\
		Success &= ReadAndCheck(  											\
			Destination, 													\
			SIZEOF_ ## FieldName ## , 										\
			1,																\
			RawReader );													\
	}																		\
	while(0)

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

/*===================================*/
/* Declarations of internal function */
/*===================================*/

/**
 * ReadAndCheck() reads from a file and checks if the reading was performed successfully.
 *
 * Accepts:
 * --------
 * 	Destination - the memory address into which the data will be written.
 * 	SizeOfField - the size of each unit of data
 * 	RequestedNumOfFields - the number of data units to read
 * 	RawReader - pointer to a Bmp24ReaderData struct representing a Line Reader object.
 *
 * Returns:
 * -------
 * TRUE if reading was successful.
 * FALSE otherwise
 */ 
static BOOL ReadAndCheck( 
	void* Destination, 
	size_t SizeOfField, 
	size_t RequestedNumOfFields, 
	Bmp24ReaderData* RawReader );

/**
 * RoundUpToMultiple() returns the smallest number that is:
 * a. Larger or equal to 'Num', and
 * b. Divisible by 'Multiple'
 */ 
static int RoundUpToMultiple( int Num, int Multiple );

// returns TRUE on success, FALSE on failure
/**
 * SeekToBeginningOfTopRow() changes the bitmap file location to the
 * start of the top row of the image. 
 *
 * Accepts:
 * -------
 * 	RawReader - pointer to a Bmp24ReaderData struct representing a Line Reader object.
 *
 * Returns:
 * -------
 * TRUE if successful.
 * FALSE otherwise
 */ 
static BOOL SeekToBeginningOfTopRow( Bmp24ReaderData* RawReader );


/**
 * BitmapFormatIsAsExpected() checks if the bitmap file is indeed in 24-bits-per-pixel format.
 *
 * Accepts:
 * -------
 * RawReader - pointer to a Bmp24ReaderData struct representing a Line Reader object.
 *
 * Returns:
 * -------
 * TRUE if file is in the expected format.
 * FALSE if file is in a different format, or if the format 
 * could not be checked successfully.
 */ 
static BOOL BitmapFormatIsAsExpected( Bmp24ReaderData* RawReader );

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

/*=============================*/
/* Implementation of functions */
/*=============================*/

Bmp24LineReader OpenBmp24LineReader( const char* Filename )
{
	__int32 Width; // exact size type to allow loading data from file into this variable
	__int32 Height; // exact size type to allow loading data from file into this variable

	BOOL Success;
	int Pitch;

	Bmp24ReaderData* RawReader = (Bmp24ReaderData*)malloc( sizeof( Bmp24ReaderData ) );
	if ( RawReader == NULL ) goto Cleanup1;

	RawReader->File = fopen( Filename , "rb" ); 
	if ( RawReader->File == NULL ) goto Cleanup2;	

	// ---------------------------
	// Read image width and height
	// ---------------------------

	if ( !BitmapFormatIsAsExpected( RawReader ) ) goto Cleanup3;

	READ_FIELD_FROM_FILE( WIDTH, &Width, RawReader, Success );
	if ( !Success ) goto Cleanup3;

	READ_FIELD_FROM_FILE( HEIGHT, &Height, RawReader, Success );
	if ( !Success ) goto Cleanup3;

	Pitch = Width * BMP24_BYTES_PER_PIXEL ;
	Pitch = RoundUpToMultiple( Pitch, 4 );

	RawReader->Width = Width;
	RawReader->Height = Height;
	RawReader->Pitch = Pitch;

	// -----------------------------------
	// Go to beginning of Top row of image (last row in file )
	// -----------------------------------

	Success = SeekToBeginningOfTopRow( RawReader );
	if ( !Success ) goto Cleanup3;

	RawReader->CurLineIndex = 0;

	// -----------------------------------
	// Succesfull Ending:
	// -----------------------------------

	return (Bmp24LineReader)RawReader;

	// -----------------------------------
	// Error handling code:
	// -----------------------------------
Cleanup3:
	fclose( RawReader->File );
Cleanup2:
	free( RawReader );
Cleanup1:
	return NULL;
}

/*.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o*/

BOOL IsInitialized( Bmp24LineReader Reader)
{
	Bmp24ReaderData* RawReader = (Bmp24ReaderData*)Reader;
	if ( Reader == NULL ) return FALSE;
	if ( RawReader->File == NULL ) return FALSE;
	return TRUE;
}

/*.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o*/

BOOL CloseBmp24LineReader( Bmp24LineReader* ReaderPtr )
{
	Bmp24ReaderData* RawReader = (Bmp24ReaderData*)(*ReaderPtr);
	int RetVal;

	if ( ReaderPtr == NULL ) return FALSE;
	if ( !IsInitialized( *ReaderPtr ) ) return FALSE;

	RetVal = fclose( RawReader->File );
	if ( RetVal == EOF ) return FALSE;

	free(RawReader);

	*ReaderPtr = NULL; // to prevent re-using the reader after it was closed, 
	// we set it to NULL.

	return TRUE;
}

/*.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o*/

EReadRowRes ReadRow( 
	Bmp24LineReader Reader, 
	Byte_t* Destination )
{
	int Res;
	BOOL Success;
	Bmp24ReaderData* RawReader = (Bmp24ReaderData*)Reader;

	if ( !IsInitialized( Reader ) ) return FAILURE;
	if ( Destination == NULL ) return FAILURE;

	if ( RawReader->CurLineIndex >= RawReader->Height )	
	{ return END_OF_FILE; }

	// Read current line:
	Success = ReadAndCheck( 
		Destination, 
		1, // SizeofField
		RawReader->Pitch, // NumOfFields
		RawReader );

	if ( !Success ) return FAILURE;

	// Advance reader to next line:
	(RawReader->CurLineIndex)++;

	if ( RawReader->CurLineIndex < RawReader->Height )
	{
		int OffsetToTwoLinesBack = (-2)*(RawReader->Pitch);

		Res = fseek( RawReader->File,				
			OffsetToTwoLinesBack,					
			SEEK_CUR );								
	
		if ( Res != 0 ) return FAILURE;
	}

	return SUCCESS;
}

/*.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o*/

#define GET_SIZE_FIELD( FieldName )								\
	Bmp24ReaderData* RawReader = (Bmp24ReaderData*)Reader;		\
	if ( !IsInitialized( Reader ) ) return 0;					\
	return ( RawReader-> ## FieldName ## )

int GetPitch( Bmp24LineReader Reader )
{
	GET_SIZE_FIELD( Pitch );
}

int GetHeight( Bmp24LineReader Reader )
{
	GET_SIZE_FIELD( Height );
}

int GetWidth( Bmp24LineReader Reader )
{
	GET_SIZE_FIELD( Width );
}

#undef GET_SIZE_FIELD

/*.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o*/

static BOOL ReadAndCheck( 
	void* Destination, 
	size_t SizeOfField, 
	size_t RequestedNumOfFields, 
	Bmp24ReaderData* RawReader )
{
	BOOL Success;

	int NumOfFieldsRead = fread(									
		Destination,											
		SizeOfField,											
		RequestedNumOfFields,											
		RawReader->File );										
																
	Success = ( ( NumOfFieldsRead == RequestedNumOfFields ) &&			
				( ferror( RawReader->File ) == 0 ) );			
																
	return Success;
}

/*.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o*/

static int RoundUpToMultiple( int Num, int Multiple )
{
	int NumOfWholeMultiples;
	int WholeMultiplesRoundedUp;
	
	if ( ( Num % Multiple ) == 0 )
	{
		return Num;
	}
	else
	{
		NumOfWholeMultiples = Num / Multiple;
		WholeMultiplesRoundedUp = NumOfWholeMultiples + 1;
		return WholeMultiplesRoundedUp * Multiple;
	}
}

/*.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o*/

static BOOL SeekToBeginningOfTopRow( Bmp24ReaderData* RawReader )
{
	int Res;
	BOOL Success;
	__int32 OffsetOfImageData; // exact size type to allow loading data from file into this variable
	int OffsetToBeginningOfLastRow;
	
	if ( !IsInitialized( RawReader ) ) return FALSE;

	READ_FIELD_FROM_FILE( LOCATION_OF_IMAGE_DATA, &OffsetOfImageData, RawReader, Success );

	if ( !Success ) return FALSE;

	Res = fseek( RawReader->File,				
		OffsetOfImageData, 
		SEEK_SET ); /* position relative to start */	
	
	if ( Res != 0 ) return FALSE;
	
	OffsetToBeginningOfLastRow = RawReader->Pitch * ( RawReader->Height - 1 ); 

	Res = fseek( RawReader->File,				
		OffsetToBeginningOfLastRow,									
		SEEK_CUR );									
	
	if ( Res != 0 ) return FALSE;

	return TRUE;
}

/*.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o*/

static BOOL BitmapFormatIsAsExpected( Bmp24ReaderData* RawReader )
{
	__int16 BitsPerPixel;
	__int32 CompressionMethod;

	BOOL Success;

	if ( RawReader == NULL ) return FALSE;

	READ_FIELD_FROM_FILE( BITS_PER_PIXEL, &BitsPerPixel, RawReader, Success );

	if ( !Success ) return FALSE;

	READ_FIELD_FROM_FILE( COMPRESSION_METHOD, &CompressionMethod, RawReader, Success );

	if ( !Success ) return FALSE;

	return ( ( BitsPerPixel == BMP24_BITS_PER_PIXEL ) && ( CompressionMethod == BMP24_COMPRESSION_METHOD ) );
}